% !Mode:: "TeX:UTF-8"

\chapter{Servlet}
本项目为《软件工程综合实践》中的第三个项目。要求开发基于Servlet + Vue的前后端分离项目。
本项目完成后，学员将能够使用VUE + Servlet + AJAX技术开发前后端分离的Web应用程序。具有主
动获取知识能力和意识及独立解决问题的能力，掌握通过查阅参考书目、开发环境
联机帮助、互联网检索等方法，获取前端应用开发相关知识及其他相关知识和技术。

\section{需求分析}

\subsection{简介}

本项目是《软件工程综合实践》课程的第三个项目。根据项目任务书，我们
需要开发一个前后端分离的Web应用程序，实现外卖点餐平台的“点餐业务线”的整体流程。项目要求
实现一个网页前端和一个后端服务器，采用前后端分离式的分工开发。作为一个团队，为了更好的
协同合作，进行高效率的分工开发，我们对这个项目进行了细致的需求分析。

\subsubsection{背景}\label{subsection:PR3beijing}
众所周知，外卖平台上的业务的运作主要涉及到这些角色：
\begin{enumerate}[label={(\arabic*)}]
    \item 用户：消费者，在平台上选择商家和该商家的食品并下单付款，最终收到骑手送来的
    食品
    \item 商家：生产者，在平台上管理自己的店铺和食品信息，接受客户发来的订单并制作食品，
    等待骑手前来取餐(或自行配送)。最终获取客户付的钱款的一部分
    \item 骑手：负责将食品从商家配送至客户，本项目中假设商家自行雇人配送
    \item 平台管理员：负责与商家交涉，在平台数据库中添加和删除商家
\end{enumerate}

在此项目中我们将目光聚焦于用户这一个角色，完成其点餐的整个过程的实现。与此同时，将实现
部分的商家与骑手相关的功能。



\subsubsection{环境搭建}
本项目因为是课程教学项目，所以对编程语言、平台和工具都有一定的要求，结合上文中给关于
项目功能方面的要求，本项目采用以下技术架构：
\begin{itemize}
    \item Jdk
    \item Servlet
    \item 使用JDBC(mysql-connector-java)实现数据库的管理
    \item Tomcat8.5
    \item MySql
    \item Vue
\end{itemize}

为方便编程，本项目采用以下开发工具：
\begin{itemize}
    \item HbuilderX
    \item STS（SpringToolSuite4）
    \item mysql
    \item Tomcat8.5
\end{itemize}

除此之外，为了方便开发和调试，我们还使用了Vscode，Apifox等开发工具。

\subsection{项目目标}\label{subsection:PR3mubiao}
\subsubsection{目标}
\begin{itemize}
    \item 实现用户的登录注册与退出功能
    \item 实现用户的浏览商家、选择商家、选择食品、地址管理、下单、购物车、支付等业务和功能
\end{itemize}
\subsubsection{功能分析}
对于用户这个角色，有以下几个待解决的问题和对系统所需实现功能的期望：
\begin{itemize}
    \item 能够通过手机号注册账户，一个手机号只能注册一次，且手机号必须符合规范
    \item 可通过手机号和密码登录系统，输入错误进行提示
    \item 用户可以退出自己的账号
    \item 系统可以存储用户的基本信息，如送货地址、用户名、手机号等
    \item 能够通过一个界面浏览商家、食品，并按照自己的喜好进行选择
    \item 可以选择食品，且同一种食品可以选择多个
    \item 选择的食品进入用户的购物车，并显示价格
    \item 用户能够地址管理，可以添加和删除多个地址
    \item 下单时可以选择地址列表里的一个地址，可以在下单页面进行修改
    \item 下单后订单信息可以在订单页面中查看
    \item 用户和商家所有信息储存在数据库上
\end{itemize}


 

\subsection{页面功能需求} \label{subsection:PR3gongneng}

各页面的功能要求如下：
\begin{enumerate}[label={(\arabic*)}]
    \item 首页页面：本页面要求能够显示显示点餐分类信息和商家信息，搜索框在屏幕向下滚动时
    要求固定在顶部，底部菜单要求固定底部，并且点击点餐分类小图片，跳转到
    商家列表页面、点击首页可以跳转至首页页面，点击订单可以跳转至我的订单页面； 

    \item 商家列表页面：本页面要求根据点餐分类显示商家列表信息，如果处于登录状态，
    那些需要查询购物车中是否有此商家的食品。如果有，在页面上显示食品数量。
    点击某个商家可以跳转至对应的商家信息页面，且有底部菜单；
    
    \item 商家信息页面：本页面要求能够显示后台商家详细信息及所属食品信
    息，并自动计算总价。若未登录，当用户点
    击添加食品时强制跳转至登陆页面，若已登录，要求能够正常增减购物车中的食品。
    点击“去结算”按钮，跳转到确认订单页面；

    \item 历史订单页面：显示用户历史订单信息，包括已支付订单和未支付订单。
    点击某个历史订单，可以对订单明细信息进行显示和隐藏。
    点击未支付订单右侧“去支付”按钮，跳转到支付界面；
    
    \item 登录页面：本页面要求用户输入错误的用户名或密码后点击登录会无法登录并提示错误
    ，若用户名和密码正确则能够登录并返回上一个页面，点击去注册则可以跳转至注册页面；
    
    \item 注册页面：本页面要求用户输入用户名时若用户名已存在则做出提示并无法注册，如果用户
    输入错误的手机号，则会提示手机号输入错误在填写完全部信息后点击注册可以成功注册并返回
    登陆页面；
    
    \item 确认订单页面：本页面要求能够显示用户的地址信息和购物车中的订单信息，用户第一
    次进入时没有默认地址并显示“请选择默认地址”，有默认地址则显示默认地址，点击送货地址可
    以跳转至送货地址列表页面，点击去支付可以跳转至在线支付页面；
    
    \item 送货地址列表页面：本页面要求能够显示用户已有的送货地址，
    点击某个送货地址，跳转回确认订单页面、点击“新增”按钮，跳转到新增送货地址页面、
    点击“编辑”按钮，跳转到送货地址编辑页面、点击“删除”按钮，删除此送货地址；
    
    \item 支付页面：本页面要求能够显示用户的订单信息，并且能够选择显示和隐藏，默认
    隐藏，并且要求阻止用户返回确认订单页面，防止重复添加订单，可以点击确认支付，
    跳转到付款码界面，完成支付

\end{enumerate}








\subsection{非功能性需求}


\subsubsection{接口}
对接口没有硬性要求，风格良好即可。

\subsubsection{界面}
项目前端是一个网页，需要美观整洁、层次分明。需要完成首页、商家列表页、订单页、地址管理页、
食品选择页等页面，页面的配色风格一致，组件图标的放置合理，需要适配不同尺寸的手机屏幕。

\section{软件架构}
根据项目任务书的要求，本项目需要采用MVC架构，以达到前后端分离式开发的目的。
项目主要分为以下模块：
\begin{itemize} 
    \item[$\bullet$] View层：前端网页。用户通过网页浏览器访问网页，通过点击和输入
    向后端服务器发送HTTP请求。
    \item[$\bullet$] 前端控制器：负责拦截、过滤、格式化、转发请求到对应的Controller，进行
    处理后，将响应返回给客户的浏览器。在本项目中我们无需编写前端控制器，只需要在
    MAVEN中添加相应的依赖即可。
    \item[$\bullet$] Po层：储存每个数据库中每个表所包含的列。
    \item[$\bullet$] Controller层：采用 SpringBoot 框架，Spring 检测到某一个类。
    有@Controller注解，便会自动将其注册为Controller。服务器运行时如果接收到
    一个HTTP请求，便会解析URL中的PATH，封装请求参数，将其传递到对应的Conctroller中。
    Controller调用对应的Service层的接口，进行进一步的处理。
    \item[$\bullet$] Service层：业务逻辑放在这一层中，调用对应的Mapper层的方法，对数据库中的
    若干表进行业务相关的操作，并包含impl层，在该层实现具体方法。
    \item[$\bullet$] Mapper层：相当于项目三中的Dao层，mapper层直接与数据库打交道（执行SQ
    L语句），接口提供给service层。
\end{itemize}

能够采用前后端分离的开发形式，进行有效的分工，提高开发效率。
经过这样的分层设计使得我们

\section{软件设计}
\subsection{后端设计}
\subsubsection{数据库设计}
数据库采用MySQL Workbench。编写Sql脚本创建数据库和表，并导入一些数据用于测试，并且查看和管理数据库。
基于需求分析，我们设计七个数据库表：商家表、食品表、购物车表、送货地址表、订单表、订单明细表、用户表。

\subsubsection{前端控制器的设计}
前端控制器负责拦截、过滤、格式化、转发请求到对应的Controller类，申请处理后，将响应返回给客户。
前端控制器拦截的"本机IP:8080/elm/"这样前缀的所有URL的请求。工作如下：
\begin{itemize} 
    \item[$\bullet$] 将请求的编码更改为UTF-8。
    \item[$\bullet$] 将Controller的类名和方法名解析出来。
    \item[$\bullet$] 调用解析出的方法。
    \item[$\bullet$] 获取调用的方法的返回值，并将返回的数据封装为JSON格式。
    \item[$\bullet$] 发送响应
    \item[$\bullet$] 异常处理 
\end{itemize}

\subsubsection{跨域问题解决方案}

除此之外，前端控制器还需要解决跨域访问的问题。考虑到本项目在未来的一段时间内没有投入使用的可能，
所以我们暂时允许所有域的访问。



\subsubsection{Controller层的设计} \label{subsection:PR3-controller}
前端控制器根据URL生成对应的Controller对象，并调用对应的成员方法，传递前端传来的数据参数。
Controller从请求中提取出需要的参数，并通过对应的Service层接口
进行后续的操作。接下来给出每一个Controller的成员方法。

$\bullet$ BusinessController:

@RequestMapping("/listBusinessByOrderTypeId")

lispublic List<Business> listBusinessByOrderTypeId(Business business)


@RequestMapping("/getBusinessById")

getpublic Business getBusinessById(Business business)


$\bullet$ CartController

@RequestMapping("/listCart")

public List<Cart> listCart(Cart cart)


@RequestMapping("/removeCart")

public int removeCart(Cart cart)

	
@RequestMapping("/saveCart")

public int saveCart(Cart cart)


@RequestMapping("/updateCart")

public int updateCart(Cart cart)


$\bullet$ DeliveryAddressController

@RequestMapping("/listDeliveryAddressByUserId")

public List<DeliveryAddress> listDeliveryAddressByUserId(DeliveryAddress deliveryAddress)


@RequestMapping("/getDeliveryAddressById")

public DeliveryAddress getDeliveryAddressById(DeliveryAddress deliveryAddress)


@RequestMapping("/saveDeliveryAddress")

public int saveDeliveryAddress(DeliveryAddress deliveryAddress)


@RequestMapping("/updateDeliveryAddress")

public int updateDeliveryAddress(DeliveryAddress deliveryAddress)


@RequestMapping("/removeDeliveryAddress")

public int removeDeliveryAddress(DeliveryAddress deliveryAddress)


$\bullet$ FoodController

@RequestMapping("/listFoodByBusinessId")

public List<Food> listFoodByBusinessId(Food food)

$\bullet$ OrdersController

@RequestMapping("/listOrdersByUserId")

public List<Orders> listOrdersByUserId(Orders orders)
	

@RequestMapping("/createOrders")

public int createOrders(Orders orders)

@RequestMapping("/getOrdersById")

public Orders getOrdersById(Orders orders)

@RequestMapping("/updateOrderStateById")

public void updateOrderStateById(Orders orders)

$\bullet$ UserControler

@RequestMapping("/getUserByIdByPass")

public User getUserByIdByPass(User user)

@RequestMapping("/getUserById")

public int getUserById(User user)


@RequestMapping("/saveUser")

public int saveUser(User user)

@RequestMapping("/updateTotalPoints")

public void updateTotalPoints(String userId, Integer totalPoints)

@RequestMapping("/getTotalPoints")

public int getTotalPoints(String userId)

@RequestMapping("/subTotalPoints")

public void subTotalPoints(String userId, Integer totalPoints)



\subsubsection{Service层设计}\label{subsection:PR3-service}

为了方便分工，Service层采用了基于接口的设计方法。负责后端的两位同学在项目初期确定
好Service层接口后，一人开发Controller层及其以上的部分，另一人开发
Service层及其以下的部分。Service层一开始提供一个简单的实现，便于上层的开发和测试。
等Service层有了完整的实现后，Controller层的实现版本只需进行简单的替换即可。



\subsubsection{Dao层设计}
Dao层也是采用了基于接口的设计方法。
根据需要可对一个Dao层接口先提供一个简单版本的实现便于Service层的编写和测试，后续
正式版本完成后进行简单的替换。




\subsection{前端设计}
首先在制作首页时让顶部搜索块在滚动条超过14\%时变为固定位置，由此让搜索块能保持在顶部显示，
然后制作底部菜单组件，使用固定位置，并用于后续页面制作。

之后点击食品类别跳转至对应商家列表页面，跳转至页面的同时获取商家列表有关信息，并判断是否
登录，如果已登录获取用户的购物车信息并判断是否显示已点食品数量。

点击商家跳转至商家信息页面，同时获取商家信息和所属食品信息，并判断是否登
录，已登录则获取用户购物车信息并显示已添加的食品数量和费用等信息；当点击添加食品时同样
也做登录验证，未登录则跳转至登陆页面，已登录则向购物车做出对应的增删改等操作。

接下来制作登陆页面，输入用户名和密码点击登录后确认用户信息，并返回上一个页面。

若点击去注册就跳转至注册页面，当用户注册时输入了用户名就首先检查用户名是否已存在，防
止重复，同时判断输入手机号是否符合规范。当全部信息填写完点击注册时添加新用户并路由
返回上一个页面。

使用正则表达式判断手机号是否正确代码如下：
\begin{lstlisting}
checkPhoneNumber() {
    var pattern =  /^1(3\d|4[5-9]|5[0-35-9]|6[5-7]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
    return pattern.test(this.user.userId);
}
\end{lstlisting}
    
\noindent\hrule
\vspace{0.1em}\noindent\hrule
    
\vspace{1em}

当用户在商家信息点击去结算时跳转至确认订单页面同时获取当前商家，获取用户购物车中在当
前商家的食品列表，之后可以点击地址栏跳转至地址管理页面或点击去支付跳转至在线支付页面
同时创建订单并清除购物车。

进入地址管理页面时首先获取用户的送货地址，如果点击某个地址，则将其存储至localStorage
中作为默认送货地址，并跳转回确认订单页面；如果点击新增收货地址则跳转至添加送货地址
页面；或者点击地址右边的按钮可以修改和删除地址，修改地址需要跳转至编辑送货地址页面。

新增收货地址和编辑送货地址页面的设计基本相同，区别仅为进入编辑送货地址页面时需要获取之
前的地址信息，新增收货地址则不需要；当地址信息填写完点击保存时向发送请求添加或修改地址
信息，并跳转回地址管理页面。

进入在线支付页面时获取订单信息，可以点击支付按钮跳转到二维码界面。
点击底部菜单的订单可以跳转至我的订单页面，向发送请求获取订单信息，可以在该页面显示
过去创建的订单。同时显示已支付订单和未支付订单，并点击在未支付订单右侧的“去支付”按钮，
可跳转到支付界面。

\section{项目团队与分工}
肖鹏飞和胡瑞琰负责项目三的前端部分。

杨乐和郑宇轩负责后端部分。



\section{项目技术}
本项目采用MVC架构，以前后端分离的形式进行开发。前端使用Vue开发
约定优于配置的原则来搭建简易MVC框架。。
后端是一个Tomcat服务器，它负责监听本机的8080端口，并把收到的请求传递给前端控制器。
Controller做的是从前端请求中抽取其传递的参数，传递给Service层。
Service层通过Dao层提供的接口访问数据库，完成对应的业务逻辑。Dao层中的每一个接口都对应数据库中
的一张表，通过JDBC完成执行SQL语句完成对数据库的查询和修改。

\subsection{前端部分}

\subsubsection{开发环境}
本项目前端部分使用HbuilderX 做IDE，基于Vue框架进行开发，同时使用后端项目进行测试。所使用的依赖如下：
\begin{itemize}
    \item [$\bullet$] npm 8.15.0
    \item [$\bullet$] VueCli 4.0.0
\end{itemize}

\subsubsection{前端Vue项目}



Vue项目通过对应的接口向后端发送请求并获取返回的信息，相关代码类似如下：

\begin{lstlisting}[ language=Java]

//根据businessId查询商家信息
this.$axios.post('BusinessController/getBusinessById', this.$qs.stringify({
    businessId: this.businessId
})).then(response => {
    this.business = response.data;
}).catch(error => {
    console.error(error);
});

\end{lstlisting}

通过路由跳转在各个页面之间切换，相关代码如下：

\begin{lstlisting}[ language==Java]
toOrder(){
	this.$router.push({path:'/orders',query:{businessId:this.business.businessId}})
}

\end{lstlisting}
\noindent\hrule
\vspace{0.1em}\noindent\hrule
    
\vspace{1em}

\subsubsection{前端Vue项目修改部分}
在每个界面中，在原本的代码中，这样的代码会导致底边栏遮挡内容
\begin{lstlisting}[ language=Java]

    .wrapper .business {
        width: 100%;
        margin-bottom: 14vw;
    };
    
\end{lstlisting}

修改为如下代码后，解决问题
\begin{lstlisting}[ language=Java]

    .wrapper .business {
        width: 100%;
        padding-bottom: 14vw;
      }
    
\end{lstlisting}

\noindent\hrule
\vspace{0.1em}\noindent\hrule
    
\vspace{1em}

在历史订单界面，判断是否为未支付订单用的如下代码

li v-for="item in orderArr" v-if="item.orderState==0"

但是该判断在失败时会导致内容不被显示

修改为如下代码可解决该问题

li v-for="item in orderArr" v-show="item.orderState == 0"
\subsection{后端部分}

\subsubsection{开发环境}
本项目后端部分使用javaSE + Servlet + MySql技术进行开发。使用Spring Tool Suite做IDE，使用MAVEN做依赖管理。
后端所用的依赖如下：

\begin{itemize}
    \item [$\bullet$] Apache Tomcat 8.5(需手动安装配置和配置)。
    \item [$\bullet$] JRE System Library[JavaSE-1.8]
    \item [$\bullet$] jackson-annotations 2.13.5
    \item [$\bullet$] jackson-core 2.13.5
    \item [$\bullet$] jackson-databind 2.13.5
    \item [$\bullet$] mysql-connector-java 5.1.49
\end{itemize}

\subsubsection{前端控制器}
上文中提到过Tomcat服务器负责监听8080端口，把收到的请求传递给前端控制器。
前端控制器更改请求的字符编码为UTF-8后解析并转发请求。
请求url与Controller方法映射示例： http://localhost:8080/elm/Controller类名/Controller方法名
该部分代码如下。

\begin{lstlisting}[ language=Java]
    // 通过Controller类全名获取此类的所有信息
    Class clazz = Class.forName("team.tjusw.elm.controller." + className);
    // 创建Controller类的对象
    // 获取Controller类对象中的方法
    Method method = clazz.getMethod(methodName, new Class[] { HttpServletRequest.class });
    // 调用上面获取的方法
    Object result = method.invoke(controller, new Object[] { request });
    // 获取向客户端响应的输出流
    out = response.getWriter();
    ObjectMapper om = new ObjectMapper();
    // 向客户端响应json数据
    out.print(om.writeValueAsString(result));
\end{lstlisting}
\noindent\hrule
\vspace{0.1em}\noindent\hrule
    
\vspace{1em}

对于方法的返回值JSON中的ObjectMapper进行转换以后将其装载到HTTP响应之中。

\subsubsection{Controller层}
Controller层的实现较为简单，他的任务只有从HTTP请求中抽取其传递的参数，传递给Service层，向前端控制器
返回结果。下面给出一个Controller层方法的代码作为例子。

\begin{lstlisting}[ language=Java]
    public Object getOrdersById(HttpServletRequest request) throws Exception {
		Integer orderId = Integer.valueOf(request.getParameter("orderId"));
		OrdersService service = new OrdersServiceImpl();
		Orders orders = service.getOrdersById(orderId);
		return orders;
	}
\end{lstlisting}
\noindent\hrule
\vspace{0.1em}\noindent\hrule
    
\vspace{1em}

\subsubsection{Service层和Dao层}
Service层实现了一些业务逻辑，这一层的实现就是一些常见的JAVA代码的写法，而Dao层的原理跟
项目一一样，所以在这里对这两层也不做赘述。

\section{项目部署}
\subsection{后端部署}
\subsubsection{开发环境检查}
此部署文档适用于Windows 10(11)操作系统。

下载STS(编码配置为UTF-8)，也可使用vscode，编码格式也为UTF-8.
Navicat for MySQL 16，MySQL，JRE 1.8等必要环
境和配置。同时下载安装好Apache Tomcat 8.5版本。

接下来给出详细流程和步骤。
\subsubsection{数据库的准备}


确保mysql数据库root账户的密码修改为"123456"。
打开Navicat，如没有需从智慧树平台进行下载安装
点击左上角的“连接”按钮，点击“MySQL”，出现以下界面:
    

按照上图进行输入，然后点击“确定”。
点击上方的“新建查询”，将elm.sql里的文本全部复制进来，点击“运行”
如果出现下图所示的数据库和表项代表数据库准备完毕






\subsubsection{配置STS}

\begin{enumerate}[label={(\arabic*)}]
    \item 1.	确保已经安装tomcat并且分支已切换至PR3后打开STS，
    点击左侧的Import Project，选择generel目录下的existing 
    project into workspace。
    \item  点击Browser按钮，选择elm的目录后点击确定。接着点击”Finish”按
    钮即可完成导入
    \item 点击window-preference-server-runtime environment
    \item 如果左侧的列表里出现了tomcat 8.5 ，那么直接点击 apply and close即可
    \item 如果左侧的列表是空的，那么点击add，在文本框里输入tomcat，选择toncat 8.5
    添加进来。然后点击apply and close
    \item 点击window-show view-others-server-servers，这样就会出现server窗口
    \item 如果server窗口里没有tomcat8.5服务器（如果有则忽略此步），点击窗
    口里的蓝色字体，在文本框中输入tomcat，然后选择tomcat8.5，点击next。 接着
    将elm项目从左侧的列表中添加到右侧的列表
    \item 双击服务器，在tomcat admin port栏填入8081
    \item 启动服务器。在浏览器里输入 localhost:8080/elm/hello.然后浏览
    器会收到505，console窗口会报异常，这是正常的
\end{enumerate}

至此，已完成本项目后端的部署工作。

\subsection{前端部署}

\subsubsection{搭建Vue工程总体框架}
接下来搭建VueCli工程模板，在工作目录打开
cmd，输入vue create elmclient，选
择Manually select features预设模板，必
要选取Babel，Router模块，选择Y使用history形式
路由，选择in package.json将依赖文件放在package.json中，是否当
当前选择保存以备下次使用任选，之后输入cd elmclien进入工程，输
入npm run serve启动工程，在浏览器中输入http://localhost:8080测试。


\subsubsection{添加其它依赖及配置文件}
首先在cmd下输入
cnpm/npm install font-awesome --save cnpm/npm install axios --save cnpm/npm install qs --save添加font-awesome与axios依赖。
根据教程添加图片至src/assets中。添加common.js，vue.config.js，main.js，App.vue，index.js等文件。


















